#pragma once

#include <cstdint>
#include <type_traits>
#include <cassert>

#include "Misc/CommonMacros.h"

struct SObjectHead;
class RObject;

struct SObjectReferenceCounter
{
	int32_t RefCount=0;

	//when object is destroyed, this pointer will be set to nullptr
	SObjectHead* ObjectHead=nullptr; 
};

namespace WH
{
    WHENGINE_API SObjectReferenceCounter *GetOrCreateObjectReferenceCounter(const RObject *InObject);

    WHENGINE_API void DestroyObject(RObject* InObject);
    
    //destroy referrence counter, and object
    WHENGINE_API void DestroyReferenceCounter(SObjectReferenceCounter* InCounter);

};

template <typename ObjectType>
class TSharedObjectPtr
{
public:     
    ObjectType* Object=nullptr;
    SObjectReferenceCounter* ObjectReferenceCounter=nullptr;

    TSharedObjectPtr()
    {
    };

    TSharedObjectPtr(std::nullptr_t )
    {
    };

    template<typename AnotherObjectType>
    TSharedObjectPtr(AnotherObjectType* InObject)
    {
        if(!InObject)
        {
            return;
        }

        assert(InObject->GetClass()->CanObjectConvertTo(ObjectType::StaticClass()));
        Object=(ObjectType*)InObject;
        ObjectReferenceCounter =WH::GetOrCreateObjectReferenceCounter((RObject*)Object);
        ObjectReferenceCounter->RefCount++;
    };

    TSharedObjectPtr(ObjectType * InObject )
    {
        if(InObject)
        {
            Object=InObject;
            ObjectReferenceCounter =WH::GetOrCreateObjectReferenceCounter((RObject*)Object);
            ObjectReferenceCounter->RefCount++;
        }
    };

    //for different object type conversion
    template<typename AnotherObjectType>
    TSharedObjectPtr(const TSharedObjectPtr<AnotherObjectType>& Other)
    {
        if(!Other.Get())
        {
            return;
        }

        assert(Other.Get()->GetClass()->CanObjectConvertTo(ObjectType::StaticClass()));
        Object=(ObjectType*)Other.Get();
        ObjectReferenceCounter = Other.ObjectReferenceCounter;
        ObjectReferenceCounter->RefCount++;
    };

    
    TSharedObjectPtr(const TSharedObjectPtr& Other)
    {
        if(!Other.Get())
        {
            return;
        }

        Object=Other.Get();
        ObjectReferenceCounter = Other.ObjectReferenceCounter;
        ObjectReferenceCounter->RefCount++;
    };



    TSharedObjectPtr(TSharedObjectPtr&& Other)
    {
        if(!Other.Get())
        {
            return;
        }
        Object=Other.Get();
        ObjectReferenceCounter = Other.ObjectReferenceCounter;

        Other.Object=nullptr;
        Other.ObjectReferenceCounter=nullptr;
    };

    template<typename AnotherObjectType>
    TSharedObjectPtr(TSharedObjectPtr<AnotherObjectType>&& Other)
    {
        if(!Other.Get())
        {
            return;
        }
        
        assert(Other.Get()->GetClass()->CanObjectConvertTo(ObjectType::StaticClass()));
        Object=(ObjectType*)Other.Get();
        ObjectReferenceCounter = Other.ObjectReferenceCounter;

        Other.Object=nullptr;
        Other.ObjectReferenceCounter=nullptr;
    };

    ~TSharedObjectPtr(){
        Clear();
    };

    void Clear()
    {
        if(ObjectReferenceCounter&&ObjectReferenceCounter->ObjectHead)
        {
            ObjectReferenceCounter->RefCount--;
            if(ObjectReferenceCounter->RefCount==0 )
            {
                WH::DestroyReferenceCounter(ObjectReferenceCounter);
            }
        }
        Object=nullptr;
        ObjectReferenceCounter=nullptr;
    }

    bool IsValid() const
    {
        return ObjectReferenceCounter&& ObjectReferenceCounter->ObjectHead;
    }

    operator bool() const
    {
        return IsValid();
    }

    TSharedObjectPtr& operator = (std::nullptr_t )
    {
        Clear();
        return *this;
    };

    template<typename AnotherObjectType>
    TSharedObjectPtr<ObjectType>& operator = (const TSharedObjectPtr<AnotherObjectType>& Other)
    {
        Clear();

        if(!Other.Get())
        {
            return *this;
        }

        assert(Other.Get()->GetClass()->CanObjectConvertTo(ObjectType::StaticClass()));

        Object=(ObjectType*)Other.Get();
        ObjectReferenceCounter = Other.ObjectReferenceCounter;
        ObjectReferenceCounter->RefCount++;
        
        return *this;
    };

    TSharedObjectPtr& operator = (const TSharedObjectPtr& Other)
    {
        //check if self assignment
        if(this==&Other)
        {
            return *this;
        }

        Clear();

        if(!Other.Get())
        {
            return *this;
        }

        Object=Other.Get();
        ObjectReferenceCounter = Other.ObjectReferenceCounter;
        ObjectReferenceCounter->RefCount++;

        return *this;
    };

    template<typename AnotherObjectType>
    TSharedObjectPtr& operator = (TSharedObjectPtr<AnotherObjectType>&& Other)
    {
        //check if self assignment
        if((void*)this==(void*)&Other)
        {
            return *this;
        }

        Clear();

        if(!Other.Get())
        {
            return *this;
        }

        assert(Other.Get()->GetClass()->CanObjectConvertTo(ObjectType::StaticClass()));
        Object=(ObjectType*)Other.Get();
        ObjectReferenceCounter = Other.ObjectReferenceCounter;

        Other.Object=nullptr;
        Other.ObjectReferenceCounter=nullptr;

        return *this;
    };

    TSharedObjectPtr& operator = (TSharedObjectPtr&& Other)
    {
        //check if self assignment
        if(this==&Other)
        {
            return *this;
        }

        Clear();

        if(!Other.Get())
        {
            return *this;
        }

        Object=Other.Get();
        ObjectReferenceCounter = Other.ObjectReferenceCounter;
        Other.Object=nullptr;
        Other.ObjectReferenceCounter=nullptr;

        return *this;
    };

    template<typename AnotherObjectType>
    TSharedObjectPtr& operator = (AnotherObjectType* InObject)
    {
        //check if self assignment
        if(Object==InObject)
        {
            return *this;
        }

        Clear();

        if(!InObject)
        {
            return *this;
        }

        assert(InObject->GetClass()->CanObjectConvertTo(ObjectType::StaticClass()));
        Object=InObject;
        ObjectReferenceCounter =WH::GetOrCreateObjectReferenceCounter((RObject*)Object);
        ObjectReferenceCounter->RefCount++;

        return *this;
    };

    TSharedObjectPtr& operator = (ObjectType * InObject)
    {    
        //check if self assignment
        if(Object==InObject)
        {
            return *this;
        }

        Clear();

        if(!InObject)
        {
            return *this;
        }

        Object=InObject;
        ObjectReferenceCounter =WH::GetOrCreateObjectReferenceCounter((RObject*)Object);
        ObjectReferenceCounter->RefCount++;

        return *this;
    };

    template <typename AnyType>
    bool operator ==(const TSharedObjectPtr<AnyType>& Other) const
    {
        return ObjectReferenceCounter == Other.ObjectReferenceCounter;
    }

    template <typename AnyType>
    bool operator ==(const AnyType* OtherObject) const
    {
    
        if(!IsValid())
        {
            return OtherObject==nullptr;
        }

        return (void*)Object == (void*)OtherObject;
    }

    ObjectType* operator -> () const
    {
        if(ObjectReferenceCounter&& ObjectReferenceCounter->ObjectHead)
        {
            return Object;
        }
        return nullptr;
    }
    ObjectType* Get() const
    {
        if(ObjectReferenceCounter&& ObjectReferenceCounter->ObjectHead)
        {
            return Object;
        }
        return nullptr;
    }

    //for std::map key requires
    bool operator < (const TSharedObjectPtr& Other) const
    {
        return ObjectReferenceCounter < Other.ObjectReferenceCounter;
    }

    uint64_t GetHashCode() const
    {
        if(!IsValid())
        {
            return 0;
        }
        return (uint64_t)Object;
    }

};



template<typename T>
struct IsSharedObjectPtr :public std::false_type
{};


template<typename T>
struct IsSharedObjectPtr<TSharedObjectPtr<T>> :public std::true_type
{};
